/*
 * form.js
 *
 * Copyright (C) 2006 - OS3 srl - http://www.os3.it
 *
 * Written by: Fabio Rotondo - fabio.rotondo@os3.it
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation;
 * version 2 of the License ONLY.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * NOTE: this is the GPL version of the library. If you want to include this 
 *       library inside a CLOSED SOURCE / PROPRIETARY software, you need 
 *       to purchase a COMMERCIAL LICENSE. See http://www.os3.it/license/
 *       for more info.
 */

/*
 * 	2009-05-18:	ADD:	form.select_set_values ()
 *
 * 	2009-05-04:	ADD:	form.events: 'start', 'before-complete', 'complete'
 *			ADD:	form.easy
 *
 *			DEL:	form.onsubmit
 *			DEL:	form.form_callback
 *
 *	2009-03-06:	form.descr () now also accepts both a string and an object as param
 */

/*
	liwe.events [ 'submit' ] = callback con parametri ( campi_form, azione, func_di_cback );
*/

/**
Form Functions
==============

**/

liwe.form = {};

liwe.form._instances = {};
liwe.form.cbacks = {};		// Internal callbacks
liwe.form.utils  = {};		// Global functions (forms only)

liwe.form._field_events = {};

liwe.form.field_event = function ( fname, id )
{
	var frm = liwe.form.get ( fname );
	return liwe.form._field_events [ id ] ( frm );
};

// liwe.form Callbacks
liwe.form.cbacks.ip_change = function ( v )
{
	console.debug ( v );
};

// {{{ liwe.form.get ( form_id )
/**
.. function:: liwe.form.get ( form_id )

	Returns the ``form`` instance identified by ``form_id``.

	:param form_id: the unique form id.

	:rtype: a valid ``form`` instance (or ``undefined`` if ``form_id`` does not exists).
**/
liwe.form.get = function ( name )
{
	return liwe.form._instances.get ( name );
};
// }}}
// {{{ liwe.form.check ( form_id )
/**
.. function:: liwe.form.check ( form_id )

        Checks if the form with ``form_id`` has been correctly compiled by the user, verifying
        all mandatory fields and checking each field against its validator.

        :param form_id: the unique form id.

        :rtype: ``true`` if the form was correctly compiled, ``false`` if it was not.
**/
liwe.form.check = function ( id )
{
	var s, t;
	var el, hint;
	var icon;
	var vals;
	var arr;

	var f = liwe.form.get ( id );

	// f.fetch_htmled_contents ();

	vals = f._mandatory;
	for ( t = 0; t < vals.length; t ++ )
	{
		el = document.getElementById ( vals [ t ] );
		hint = document.getElementById ( vals [ t ] + "@hint" );
		icon = document.getElementById ( vals [ t ] + "_mandatory" );

		if ( ! el ) continue;

		hint.innerHTML = "&nbsp;";

		if ( el.value.length == 0 ) 
		{
			hint.innerHTML = _ ( "os3_form", "Mandatory&nbsp;field" );

			icon.src = liwe._libbase + "/gfx/form/error.png";
			el.focus ();
			return false;
		}
		icon.src = liwe._libbase + "/gfx/form/ok.png";
	}

	vals = f._validates;
	for ( t = 0; t < vals.length; t ++ )
	{
		el = document.getElementById ( vals [ t ] [ 0 ] );
		icon = document.getElementById ( vals [ t ] [ 0 ] + "_valid" );
		hint = document.getElementById ( vals [ t ] [ 0 ] + "@hint" );
		if ( ! el ) continue;

		hint.innerHTML = "&nbsp;";
		if ( el.value.length == 0 ) continue;

		if ( vals [ t ] [ 1 ] ( el.value ) == false )
		{
			hint.innerHTML = _ ( "os3_form", "Validation&nbsp;failed" );	
			icon.src = liwe._libbase + "/gfx/form/error.png";
			icon.alt = _ ( "os3_form", "Validation failed" );
			icon.title = _ ( "os3_form", "Validation failed" );
			el.focus ();
			return false;
		}
		icon.src = liwe._libbase + "/gfx/form/ok.png";
		icon.alt = _ ( "os3_form", "Validation OK" );
		icon.title = _ ( "os3_form", "Validation OK" );
	}

	return true;
};
// }}}
// {{{ liwe.form.cbacks.ajax_submit ( id )
liwe.form.cbacks.ajax_submit = function ( id )
{
	var frm = liwe.form._instances [ id ];

	if ( ! frm.check () ) return;

	if ( frm.events [ 'start' ] )  
	{
		var r = frm.events [ 'start' ] ( frm );
		if ( ! r ) return;
	}

	var a = frm.get_values ();
	var action = frm._resolve_action ();
	
	// Resolve function pointer from function name in frm.events [ 'complete' ]
	eval ( "var _func = " + frm.events [ 'complete' ] );

	if ( ! frm.events [ 'submit' ] )
	{
		// AJAX Request
		liwe.AJAX.request ( action, a, _func, true );
	} else {
		frm.events [ 'submit' ] ( a, action, _func );
	}
};
// }}}

// SPECIAL:
//	multi, bicolor, columns, width, height
function form_sel ( vars )
{
	if ( typeof OS3Sel == 'undefined' ) 
	{
		alert ( _ ( "os3_form", "You *MUST* include OS3Sel!" ) );
		return null;
	}

	this._start_field ( vars );

	vars [ 'id' ] = this.name + "@" + this._get_name ( vars );

	this.html += '<div id="' + vars [ 'id' ] + '"></div>';

	this._newline ( vars );

	var g = new OS3Sel ( 
		{ 	div_id: vars [ 'id' ], 
			multi: vars.get ( 'multi', 0 ), 
			bicolor: vars.get ( 'bicolor', true ),
			num_columns: vars.get ( 'columns', 1 ),
			width: vars.get ( 'width', '100%' ),
			height: vars.get ( 'height', '100%' )
		} );

	if ( vars [ 'headers' ] ) g.set_headers ( vars [ 'headers' ] );

	return g;
}

// {{{ liwe.form.instance ( name, action, no_table )
/**
.. function:: liwe.form.instance ( form_id, action )

        Use this function to create a new LiWE Form. 

        :param form_id:    the form id. You must ensure this is an unique value.

        :param action:     the custom form action to be performed on submit. (Optional)
                           If it not defined, then ``ajax_mode`` will be used (if present) otherwise ``liwe.AJAX.url``.

	.. code-block:: javascript

			var f = new liwe.form.instance ( "test" );

.. class:: Form

        The LiWE Form has the following methods and attributes

**/
liwe.form.instance = function ( name, action, no_table )
{
	this.name	= name;
	this.action	= action;
	this.easy	= true;

	this.events	= { 'start' : null, 'before-complete' : null, 'complete': null, 'submit' : null };

	this.no_table	= no_table;
	// this.auto_id	= true;

	this.multipart 	= false;
	// this.form_callback = '';

	this.html	= '';

	this.ajax_mode	= null;

	this.debug	= false;

	this._first_field = null;

	this._field_extensions = [];

	this._radios = {};
	this._ignore = {};

	// NEW METHODS
	this.register_field_extension = function ( ext_name, ext_cback, skip_name )
	{
		var ext = { name: ext_name, cback: ext_cback, skip: skip_name };

		if ( skip_name ) this.skips.push ( skip_name.toLowerCase () );

		this._field_extensions.push ( ext );
	};

	// PUBLIC METHODS
	// {{{ upload ( vars )
/**
        .. method:: upload ( vals )

                this method adds an upload field to the form. This is a standard upload field with the browser
                showing the ``Browse`` button to add a file to the form.

                When an ``upload`` field is present, the form switches automatically to ``multipart``.

        
**/
	this.upload = function ( vars )
	{
		this.multipart = true;

		this._start_field ( vars );
		this._add_field ( 'file', vars );
	};
	// }}}
	// {{{ text ( vars )
/**
        .. method:: text ( vals )

                this method adds a standard text input field.
**/
	this.text = function ( vars )
	{
		this._start_field ( vars );
		this._add_field ( 'text', vars );
	};
	// }}}
	// {{{ email ( vars )
/**
        .. method:: email ( vals )

                this method adds a text field behaving as an email input field. The user will be able to insert only
                lowercase letters from a to z, numbers, ".", "@" and "_". Any other character will be avoided.
                The content of an ``email`` field will be also checked against the ``email validator``.
**/
	this.email = function ( vars )
	{
		vars [ 'autocapitalize' ] = 'Off';

		this._set_values ( "email", vars );

		this._start_field ( vars );
		this._add_field ( 'text', vars );
	};
	// }}}
	// {{{ number ( vars )
/**
        .. method:: number ( vals )

                this method adds a text field behaving as a number input field. The user will be able to insert only
                numbers and the "-" char. Any other character will be avoided.
                The content of a ``number`` field will be also check agains the ``is_integer`` validator.
**/
	this.number = function ( vars )
	{
		this._set_values ( "number", vars );

		this._start_field ( vars );
		this._add_field ( 'number', vars );
	};
	// }}}
	// {{{ money ( vars )
/**
        .. method:: money ( vals )

                this method adds a text field behaving as a money input field. The user will be able to insert only
                numbers, "." and "," chars. Any other character will be avoided.
**/
	this.money = function ( vars )
	{
		if ( typeof Money == 'undefined' ) liwe.utils.append_js ( liwe._libbase + '/money.js' );

		this._set_values ( "money", vars );

		this._start_field ( vars );
		this._add_field ( 'text', vars );
	};
	// }}}
	// {{{ submit ( vars )
/**
        .. method:: submit ( vals )

                this method adds a standard ``submit`` field to the form. ``vals`` can be a dictionary of properties
                or a simple ``string``. If vals is a ``string``, then it will be converted to the dictionary:
                ``{ value: vals }``.

                .. code-block:: javascript

                        f.submit ( "Go on!" );

                        // also
                        f.submit ( { value: "Go on!" } );
**/
	this.submit = function ( vars )
	{
		if ( typeof ( vars ) == 'string' ) vars = { value: vars };

		this._start_field ( vars );

		if ( this.multipart )
			this._add_field ( 'submit', vars );
		else
		{
			vars [ 'onclick' ] = "liwe.form.cbacks.ajax_submit('" + this.name + "');";
			this._add_button ( vars );
		}
	};
	// }}}
	// {{{ label ( vars )
/**
        .. method:: label ( vals )

                this methods adds a non editable text line to the form.

                .. code-block:: javascript

                        f.label ( { value: "Hello world!" } );
**/
	this.label = function ( vars )
	{
		this._start_field ( vars );
		this.html += vars [ 'value' ];
		this._newline ( vars );
	};
	// }}}
	// {{{ password ( vars )
/**
        .. method:: password ( vals )

                this method adds a standard password input field.

                .. code-block:: javascript

                        f.password ( { label: "Password", name: "passwd", size: 20, maxlength: 20, mandatory: true } );
**/
	this.password = function ( vars )
	{
		this._start_field ( vars );
		this._add_field ( 'password', vars );
	};
	// }}}
	// {{{ checkbox ( vars ) 
/**
        .. method:: checkbox ( vals )

                this method adds a standard checkbox field to the form.

                .. code-block:: javascript

                        f.checkbox ( { label: "save my password", name: "save_pwd", checked: false } );
                        f.checkbox ( { label: "log me anonymously", name: "anon", checked: true } );
**/
	this.checkbox	= function ( vars )
	{
		this._start_field ( vars );
		this._add_field ( 'checkbox', vars );
	};
	// }}}
	// {{{ hidden ( vars, val )
/**
        .. method:: hidden ( vals, [val] )

                this method adds an hidden field to the form. Since an hidden input field can only have
                two params (name and value), you can use the ``hidden`` method in two ways: by passing
                a dictionary with { name: "xxx", value: "yyy" }, or by simply passing: "xxx", "yyy" as parameters.

                .. code-block:: javascript
                              
                        f.hidden ( { name: "hidden1", value: "the value" } );
                        f.hidden ( "hidden2", "the value 2" );
**/
	this.hidden = function ( vars, val )
	{
		/*
		NOTA: il metodo f.hidden() puo' essere usato in due modi:

			1 - La sintassi standard a "dizionario", cosi': f.hidden ( { name: 'nome_campo', value: 'valore' } );
			2 - Passando due parametri al posto del dizionario: f.hidden ( "module", "shop" );
			    che corrispondono a "name" e "value". 

			Nel secondo caso, il campo "id" viene valorizzato di default.
		*/
		if ( typeof ( val ) != 'undefined' )
		{
			var id = this.name + "@" + vars;

			this._fields.push ( vars );

			this.html += '<input type="hidden" name="' + vars + '" id="' + id + '" value="' + val + '" ';
			this.html += ' />';
		} else {
			var id;

			if ( ! vars [ 'id' ] )
				id = this.name + "@" + vars [ 'name' ];
			else
				id = this.name + "@" + vars [ 'id' ];

			this._fields.push ( vars [ 'name' ] );

			this.html += '<input type="hidden" id="' + id + '" name="' + vars [ 'name' ] + '" value="' + vars [ 'value' ] + '" ';
			// if ( vars [ 'id' ] ) this.html += ' id="' + vars [ 'id' ] + '" ';
			if ( vars [ 'onchange' ] ) this.html += ' onchange="' + vars [ 'onchange' ] + '" ';
			this.html += ' />';
		}
	};
	// }}}
	// {{{ select ( vars ) 
/**
        .. method:: select ( vals )

                this method creates a select form field.
                This method has some special ``vals`` keys you can provide:

                        - ``options``:          a list of options for the select field.
                                                The list **must** be an array of dictionaries with ``label`` and ``value`` fields.

                        - ``force_select``:     this is a boolean flag. If ``true``, the ``select`` will start with a custom string
                                                prompting a selection.

                .. code-block:: javascript

                        var opts = [
                                        { label: "opt 1", value: "1" },
                                        { label: "opt 2", value: "2" },
                                        { label: "opt 3", value: "3" }
                                   ];

                        f.select  ( { label: "a select", name: "sel", options: opts, force_select: true } );
**/
	this.select = function ( vars )
	{
		this._start_field ( vars );

		var s = '<select ';
		var k;
		var skips = "{" + this._skips.join ( '}{' ) + "}";
		var sel_val = -1;

		vars [ 'id' ] = this.name + "@" + this._get_name ( vars );

		s += ' id="' + vars [ 'id' ] + '" ';

		if ( ( vars [ 'defval' ] != undefined ) && ( vars [ 'value' ] == undefined ) ) vars [ 'value' ] = vars [ 'defval' ];
		
		for ( k in vars )
		{
			if ( typeof vars [ k ] == 'function' ) continue;	// Skip functions
			if ( skips.indexOf ( '{' + k.toLowerCase () + '}' ) != -1 ) continue;
			s += k + '="' + vars [ k ] + '" ';
		}

		s += '>';

		this.html += s;

		if ( vars [ 'value' ] != undefined ) sel_val = vars [ 'value' ];

		s = '';
		if ( vars [ 'force_select' ] ) s += '<option value="">' + _ ( "os3_form", "(Selezionare)" ) + '</option>';

		if ( vars [ 'options' ] )
		{
			var opts = Array.fromObject ( vars [ 'options' ] );	
			var l = opts.length;
			var t, opt;

			for ( t = 0; t < l; t ++ )
			{
				opt = opts [ t ];
				s += '<option value="' + opt [ 'value' ] + '" ';
				if ( opt [ 'class' ] ) s += ' class="' + opt [ 'class' ] + '" ';
				if ( ( opt [ 'selected' ] || ( sel_val == opt [ 'value' ] ) ) ) s += ' selected="selected" ';
				s += '>' + opt [ 'label' ] + '</option>';
			}
		}
		
		s += '</select>';
		this.html += s;

		if ( vars [ 'mandatory' ] )	this._mandatory.push ( vars [ 'id' ] );

		this._create_hint ( vars );
		this._newline ( vars );
	};
	// }}}
	// {{{ select_set_values ( field_name, values )
/**
        .. method:: select_set_values ( field_name, options )

                this method will overwrite all options inside a ``select`` field with the new ones provided
                by ``options``. As for the ``Form.select`` method, the ``options`` list must be an array of
                dictionaries with ``label`` and ``value`` fields.
**/
	this.select_set_values = function ( field_name, values )
	{
		var t, l = values.length, e;
		var select = this.get_element ( field_name );

		while ( select.hasChildNodes () ) {
			select.removeChild ( select.lastChild );
		};
			
		if ( select.getAttribute ( 'force_select' ) == 'true' )
		{
			e = new Option ( '(Selezionare)', "" , false, false );
			select.options [ select.options.length ] = e;
		}
		
		for ( t = 0; t < l; t ++ )
		{
			e = new Option ( values [ t ] [ 'label' ], values [ t ] [ 'value' ], false, false );
			select.options [ select.options.length ] = e;
		}
	};
	// }}}
	// {{{ sep ( vars )
/**
        .. method:: sep ()

                this method simply draws a line separator inside the form.

                .. code-block:: javascript

                        f.sep ();

**/
	this.sep = function ( vars )
	{
		this._newline ( {} );

		if ( ! this.no_table ) this.html += '<tr><td colspan="200">';
		this.html += '<hr />';
		this._newline ( {} );
	};
	// }}}
	// {{{ textarea ( vars ) 
/**
        .. method:: textarea ( vals )

                this method creates a textarea form field.
                This method has some special ``vals`` keys you can provide:

                        - ``text``:             the textarea text
                        - ``show_entity``:      a flag ``true``, ``false``. If ``true`` any entity in the text will be shown as HTML entity.
                        - ``code``:             a flag ``true``, ``false``. If ``true``, the textarea will be used with monospace font.

                .. code-block:: javascript

                        f.textarea ( { label: "txt", name: "txt", rows: 10, cols: 80 } );
                        f.textarea ( { label: "code", name: "code", rows: 20, cols: 80, code: true } );
**/
	this.textarea = function ( vars )
	{
		this._start_field ( vars );

		var s = '<textarea ';
		var k;
		var skips = "{label}{mandatory}{akey}{hint}{text}{nonl}{os3_full}{defval}{value}{show_entity}{code}";  

		vars [ 'id' ] = this.name + "@" + this._get_name ( vars );
		if ( ( vars [ 'value' ] ) && ( ! vars [ 'text' ] ) ) vars [ 'text' ] = vars [ 'value' ];
		if ( ( vars [ 'defval' ] != undefined ) && ( vars [ 'text' ] == undefined ) ) vars [ 'text' ] = vars [ 'defval' ];
		
		for ( k in vars )
		{
			if ( skips.indexOf ( '{' + k.toLowerCase () + '}' ) != -1 ) continue;
			if ( typeof vars [ k ] == 'function' ) continue;	// Skip functions
			s += k + '="' + vars [ k ] + '" ';
		}

		if ( vars [ 'akey' ] ) s += ' accesskey="' + vars [ 'akey' ] + '" ';
		if ( vars [ 'code' ] ) s += ' style="font-family: Courier, monospace; font-size: 80%;" ';

		s += '>';

		if ( vars [ 'text' ] ) 
		{
			if ( vars [ 'show_entity' ] )
				s += vars [ 'text' ].replace ( new RegExp ( "&([a-zA-Z][a-z]+);", "g" ), "&amp;$1;" ).replace ( new RegExp ( "<br[^>]*>", "g" ), "\n" );
			else
				s += vars [ 'text' ];
		}

		s += '</textarea>';

		this.html += s;

		if ( vars [ 'mandatory' ] ) this._mandatory.push ( vars [ 'id' ] );

		this._create_hint ( vars );
		this._newline ( vars );
	};
	// }}}
	// {{{ button ( vars )
/**
        .. method:: button ( vals )

                this mtehod creates a standard form button.

                .. code-block:: javascript

                        f.button ( { value: "Show me", onclick: "do_show()" } );
**/
	this.button = function ( vars )
	{
		this._start_field ( vars );

		vars [ 'id' ] = this.name + "@" + this._get_name ( vars );
		this._add_button ( vars );

		this._newline ( vars );
	};
	// }}}
	// {{{ descr ( vars ) 
/**
        .. method:: descr ( vals )

                this methods adds a non editable text field to the form. The text shown (as the ``value`` key) can be
                any valid HTML code, not just text.

                .. code-block:: javascript

                        f.vals ( { name: "Hello", value: user_name } );
**/
	this.descr = function ( vars )
	{
		if ( typeof ( vars ) == "string" ) vars = { "text" : vars };
		this._newline ( {} );
		if ( ! this.no_table ) this.html += '<tr><td colspan="200" class="text_descr">';
		this.html += vars [ 'text' ];
		this._newline ( {} );
	};
	// }}}
	// {{{ error_msg ( txt )
/**
        .. method:: error_msg ( txt )

                this method shows the error ``txt`` inside the form error box.

                .. code-block:: javascript

                        f.error_msg ( "Please, review your details!" );
**/
	this.error_msg	= function ( txt )
	{
		var e = $( this.name + "_err_box" );

		if ( ! e ) console.error ( "No error box for form: " + this.name );

		if ( txt )
		{
			e.innerHTML = txt;
			e.style.visibility = 'visible';
		} else
			e.style.visibility = 'hidden';
	};
	// }}}
	// {{{ get ()
/**
        .. method:: get ()

                this method returns the form HTML code.

                .. code-block:: javascript

                        var code = f.get ();
**/
	this.get = function ()
	{
		var start = '';
		var end   = '';
		var cback = '';
		var action = this._resolve_action ();

		if ( ! this.name ) this.name = "no_name";

		start += '<form method="post" name="' + this.name + '" id="' + this.name + '" ' + 
			 ' action="' + action + '" ' + 
			 ' onsubmit="return liwe.form._aim.submit(this,{onStart: liwe.form._event_on_start, onComplete: liwe.form._event_on_complete})">';

		if ( this.no_table  )
		{
			start += '<div class="frm" id="' + this.name + '_frm" >';
			end   += '</div>';
		} else {
			start += '<table border="0" class="frm" id="' + this.name + '_frm" cellpadding="0" cellspacing="0" >';
			end   += '</table>';
		}

		return start + this.html + end + '</form>';
	};
	// }}}
	// {{{ set ( id_dest )
/**
        .. method:: set ( id_dest )

                this method renders the form inside ``id_dest`` HTML container.

                .. code-block:: javascript

                        f.set ( 'dest_div' );
**/
	this.set = function ( id_dest )
	{
		var el = $( id_dest ); 

		if ( ! el )
		{
			console.error ( "Form.set(): object '%s' not found in DOM", id_dest );
			return;
		}

		el.innerHTML = this.get ();
		this._render_all ();

		if ( this._first_field )
			this.set_focus ( this._first_field );
	};
	// }}}
	// {{{ _resolve_action ()
	this._resolve_action = function ()
	{
		if ( this.action ) return this.action;
		if ( this.ajax_mode ) return this.ajax_mode;
		if ( liwe.AJAX && liwe.AJAX.url ) return liwe.AJAX.url;

		console.error ( "Form: %s does not have an 'action' value set", this.name );
		return '';
	};
	// }}}
	// {{{ error_box ( vars ) 
/**
        .. method:: error_box ( vals )

                this method creates an error_box area inside the form.

                .. warning:: this function is not yet fully supported and may change in the future.

                .. code-block:: javascript

                        f.error_box ( { label: "notes", name: "err_box" } );
**/
	this.error_box = function ()
	{
		var s;
		var vars = Array ();

		vars [ 'os3_full' ] = 1;

		this._start_field ( vars );

		// vars [ 'id' ] = this.name + "@" + this._get_name ( vars );
		s = 'border: 1px solid red; padding: 4px; background-color: #FFE87F; color: black; visibility: ' + ( vars [ 'show' ] ? 'visible' : 'hidden' );

		// this.html += '<div id="' + vars [ 'id' ] + '" style="' + s + '"></div>';
		this.html += '<div id="' + this.name + '_err_box" style="' + s + '"></div>';
		this._newline ( vars );
	};
	// }}}
	// {{{ workspace ( vars )
/**
        .. method:: workspace ( vals )

                this method creates an empty div inside the form to be able to render anything else inside it.

                .. code-block:: javascript

                        f.workspace ( { name: "my_workspace" } );
**/
	this.workspace		= function ( vars )
	{
		this._newline ( {} );
		if ( ! this.no_table ) this.html += '<tr><td colspan="200">';
		this.html += '<div id="' + vars [ 'name' ] + '"></div>';
		this._newline ( {} );
	};
	// }}}
	// {{{ check ()
/**
        .. method:: check ()

                this method checks if the form has been correctly filled. See ``liwe.form.check()``
**/
	this.check = function () { return liwe.form.check ( this.name ); };
	// }}}
	// {{{ get_value ( widget_name )
/**
        .. method:: get_value ( input_field_name )

                this method returns the value of the specified ``input_field_name``.

                .. code-block:: javascript

                        var login = f.get_value ( "login" );
**/
	this.get_value	= function ( widget_name )
	{
		if ( this._widgets [ widget_name ] )
		{
			var w = this._widgets [ widget_name ];
			return w.get_value ();
		}

		var name = this.name + "@" + widget_name;
		var e;

		if ( name in this._radios )
		{
			var lst = this._radios [ name ];
			var t, l = lst.length;

			for ( t = 0; t < l; t ++ )
			{
				e = document.getElementById ( lst [ t ] );
				if ( e.checked ) return e.value;
			}
		} else {
			e = document.getElementById ( name );
			if ( ! e ) return null;
		}

		if ( ( e.type == 'checkbox' ) && ( ! e.checked ) ) return false;

		//var value = encodeURIComponent ( e.value );
		var value = e.value;

		return value;
	};
	// }}}
	// {{{ set_value ( widget_name, value )
/**
        .. method:: set_value ( input_field_name, val )

                this method sets the value of the specified ``input_field_name`` to ``val``.

                .. code-block:: javascript

                        f.set_value ( "login", "your login here" );
**/
	this.set_value	= function ( widget_name, value )
	{
		//console.debug ( "SET VALUE: %s - v: %s", widget_name, value );

		if ( this._widgets [ widget_name ] )
		{
			this._widgets [ widget_name ].set_value ( value );
			return;
		}

		var e = document.getElementById ( this.name + "@" + widget_name );
		if ( ! e ) 
		{
			e = document.getElementById ( this.name + "@" + widget_name + "-" + value );
			if ( ! e )
			{
				console.warn ( "Form: could not find input '%s'", this.name + "@" + widget_name );
				return;
			}
		}

		switch ( e.type )
		{
			case "checkbox":
				if ( typeof value == 'undefined' ) value = false;
				e.checked = value;
				break;

			case "radio":
				e.checked = true;
				/*
				var radios = document.getElementsByName( widget_name);
				for (var i = 0; i < radios.length; i++) {
					radios[i].checked = (radios[i].value == value);
				};
				*/
			default:
				if ( typeof value == 'undefined' ) value = '';
				e.value = value;
		}
	};
	// }}}
	// {{{ set_focus ( widget_name )
/**
        .. method:: set_focus ( input_field_name )

                this method sets focus to the specified ``input_field_name``.

                .. code-block:: javascript

                        f.set_focus ( "login" );
**/
	this.set_focus = function ( widget_name )
	{
		if ( this._widgets [ widget_name ] )
		{
			var w = this._widgets [ widget_name ];
			w.set_focus ();
			return;
		}

		var e = document.getElementById ( this.name + "@" + widget_name );
		if ( ! e ) return;

		try
		{
			e.focus ();
			e.select ();
		} catch ( e ) {};
	};
	// }}}
	// {{{ get_element ( widget_name )
/**
        .. method:: get_element ( input_field_name )

                this method returns the DOM element associated to the ``input_field_name`` form field.

                .. code-block:: javascript

                        var inp = f.get_element ( "login" );
**/
	this.get_element = function ( widget_name ) { return document.getElementById ( this.name + "@" + widget_name ); };
	// }}}
	// {{{ get_values ()
/**
        .. method:: get_values ()

                this method returns a dictionary with all the key (field names) and their respective values.

                .. code-block:: javascript

                        var vals = f.get_values ();
**/
	this.get_values	= function ()
	{
		var res = {};
		var self = this;

		this._fields.iterate ( function ( v )
			{
				if ( v in self._ignore ) return;
				res [ v ] = self.get_value ( v );
			} );
		// var a = Array.fromForm ( this.name + "_frm" );
		return res;
	};
	// }}}
	// {{{ clear ()
/**
        .. method:: clear ()

                this method empties the form.

                .. code-block:: javascript

                        f.clear ();
**/
	this.clear = function ()
	{
		function _clear_input ( lst )
		{
			var l = lst.length;
			var t;

			for ( t = 0; t < l; t ++ ) 
			{
				if ( lst [ t ].type.toUpperCase () == 'CHECKBOX' )
					lst [ t ].checked = false;
				else
					lst [ t ].value = "";
			}
		}

		var frm = $( this.name + "_frm" );

		_clear_input ( frm.getElementsByTagName ( 'input' ) );
		_clear_input ( frm.getElementsByTagName ( 'select' ) );
		_clear_input ( frm.getElementsByTagName ( 'textarea' ) );
	};
	// }}}
	// {{{ radio ( vars )
/**
        .. method:: radio ( vals )

                this method adds a radio button to the form.

                .. warning:: this method is unstable and may not work properely.

                .. code-block:: javascript

                        f.radio ( { label: "hello", name: "rad", value: "1" } );
                        f.radio ( { label: "hello", name: "rad", value: "2" } );
                        f.radio ( { label: "hello", name: "rad", value: "3" } );
**/
	this.radio = function ( vars )
	{
		this._start_field ( vars );
		this._add_field ( 'radio', vars );
	};
	// }}}
	// {{{ datetime ( vars )
	this.datetime = function ( vars )
	{
		vars [ 'size' ] = 19;
		vars [ 'maxlength' ] = 19;
		vars [ 'filter' ] = "0123456789-: ";
		vars [ 'validator' ] = true;
		vars [ 'os3_def_validator' ] = liwe.validators.is_datetime;
		vars [ 'title' ] = 'YYYY-MM-DD hh:mm:ss';

		this._start_field ( vars );
		this._add_field ( 'text', vars );
	};
	// }}}
	// {{{ date ( vars )
	this.date = function ( vars )
	{
		vars [ 'size' ] = 10;
		vars [ 'maxlength' ] = 10;
		vars [ 'filter' ] = "0123456789-: ";
		vars [ 'validator' ] = true;
		vars [ 'os3_def_validator' ] = liwe.validators.is_date;
		vars [ 'title' ] = 'YYYY-MM-DD';

		this._start_field ( vars );
		this._add_field ( 'text', vars );
	};
	// }}}

	// NOT DOCUMENTED
	// {{{ grid ( vars )  - ******************************* 
	this.grid = function ( vars )
	{
		if ( typeof OS3Grid == 'undefined' ) 
		{
			alert ( _ ( "os3_form", "You *MUST* include OS3Grid" ) );		
			return null;
		}

		var name = vars [ 'name' ];

		this._start_field ( vars );

		vars [ 'id' ] = this.name + "@" + this._get_name ( vars );

		this.html += '<div id="' + vars [ 'id' ] + '"></div>';

		this._newline ( vars );

		var g = new OS3Grid ( vars [ 'name' ] );

		g.width = "100%";
		g.height = "100%";
		g.highlight = true;
		g.div_id    = vars [ 'id' ];

		if ( vars [ 'headers' ] ) g.set_headers ( vars [ 'headers' ] );

		return g;
	};
	// }}}
	// {{{ add_wiget ( vars ) 
	this.add_widget		= function ( vars )
	{
		this._start_field ( vars );
		this.html += vars [ 'widget' ];

		this._newline ( vars );
	};
	// }}}
	// {{{ replace ( base_div_id )
	this.replace = function ( base_div_id )
	{
		function _get_opts ( el )
		{
			var opts = el.getElementsByTagName ( "option" );
			var t, l = opts.length, opt;
			var res = [];

			if ( el.getAttribute ( "force_select" ) == 1 ) res.push ( { label: "(Selezionare)", value: "" } );

			for ( t = 0; t < l; t ++ )
			{
				opt = opts [ t ];
				res.push ( { label: opt.innerHTML, value: opt.value } );
			}

			return res;
		}

		var base_div = document.getElementById ( base_div_id );
		var t, l, elems;
		var self = this;

		[ "input", "textarea" ].iterate ( function ( tag_name )
		{
			elems = base_div.getElementsByTagName ( tag_name );
			for ( t = 0; t < elems.length; t ++ )
				self._create_field ( elems [ t ] );
		} );

	
		elems = base_div.getElementsByTagName ( "select" );
		l = elems.length;

		for ( t = 0; t < l; t ++ )
		{
			opts = _get_opts ( elems [ t ] );
			this._create_field ( elems [ t ] );

			this.select_set_values ( elems [ t ].name, opts );
		}
	};
	// }}}
	// {{{ ipaddr ( vars )
	this.ipaddr = function ( vars )
	{
		var split = {};

		this._start_field ( vars );
		// vars [ 'id' ] = this.name + "@" + this._get_name ( vars );

		if ( ( vars [ 'defval' ] != undefined ) && ( vars [ 'value' ] == undefined ) ) vars [ 'value' ] = vars [ 'defval' ];

		if ( vars [ 'value' ] ) split = vars [ 'value' ].match ( new RegExp ( "([1-9][0-9]{0,2})\.([1-9][0-9]{0,2})\.([1-9][0-9]{0,2})\.([1-9][0-9]{0,2})" ) );

		if ( ! vars [ 'onchange' ] )
			vars [ 'onchange' ] = "liwe.form.cbacks.ip_change ( this )";
		else
			vars [ 'onchange' ] = "liwe.form.cbacks.ip_change ( this );" + vars [ 'onchange' ];

		this.html += '<table border="0" cellpadding="0" cellspacing="0"><tr><td>';

		var v = Array ();

		v [ 'filter' ] = "0123456789";
		v [ 'class'  ] = "number";
		v [ 'os3_def_validator' ] = liwe.validators.is_integer;
		v [ 'nonl' ] = 1;
		v [ 'onchange' ] = vars [ 'onchange' ];

		v [ 'name' ] = vars [ 'name' ] + "@ip1";
		v [ 'id'   ] = vars [ 'name' ] + "@ip1";
		v [ 'size' ] = 3;
		v [ 'maxlength' ] = 3;
		v [ 'title' ] = "IP 1";
		v [ 'value' ] = split [ 1 ];
		
		this._add_field ( 'text', v  );
		this.html += '.</td><td>';

		v [ 'name' ] = vars [ 'name' ] + "@ip2";
		v [ 'id'   ] = vars [ 'name' ] + "@ip2";
		v [ 'size' ] = 3;
		v [ 'maxlength' ] = 3;
		v [ 'title' ] = "IP 2";
		v [ 'value' ] = split [ 2 ];


		this._add_field ( 'text', v  );
		this.html += '.</td><td>';

		v [ 'name' ] = vars [ 'name' ] + "@ip3";
		v [ 'id'   ] = vars [ 'name' ] + "@ip3";
		v [ 'title' ] = "IP 3";
		v [ 'value' ] = split [ 3 ];

		this._add_field ( 'text', v  );
		this.html += '.</td><td>';

		v [ 'name' ] = vars [ 'name' ] + "@ip4";
		v [ 'id'   ] = vars [ 'name' ] + "@ip4";
		v [ 'title' ] = "IP 4";
		v [ 'value' ] = split [ 4 ];
		this._add_field ( 'text', v  );
		this.html += '</td>';

		this._create_hint ( vars );

		if ( vars [ 'id' ] ) this.html += '</td><td><div id="' + vars [ 'id' ] + '"></div>';
		this.html += '</td></tr></table>';

		this._newline ( vars );
	};
	// }}}
	// {{{ cod_fisc ( vars )
	this.cod_fisc = function ( vars )
	{
		vars [ 'size' ] = 16;
		vars [ 'maxlength' ] = 16;
		vars [ 'filter' ] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
		vars [ 'os3_def_validator' ] = liwe.validators.is_codice_fiscale;

		this._start_field ( vars );
		this._add_field ( 'text', vars );
	};
	// }}}
	// {{{ p_iva ( vars )
	this.p_iva = function ( vars )
	{
		vars [ 'size' ] = 11;
		vars [ 'maxlength' ] = 11;
		vars [ 'filter' ] = "0123456789";
		vars [ 'os3_def_validator' ] = liwe.validators.is_partita_iva;

		this._start_field ( vars );
		this._add_field ( 'text', vars );
	};
	// }}}
	

	// UNSTABLE
	// {{{ fetch_htmled_contents ()
	// FIXME: THIS FUNCTION DOES NOT WORK ANYMORE!
	this.fetch_htmled_contents = function ()
	{
		console.error ( "form.fetch_htmled_contents() has been REMOVED" );
		/*
		var i = this._htmled.length;
		var t, n;

		if ( ! i ) return;

		for ( t = 0; t < i; t ++ )
		{
			// Field name
			n = this._htmled [ t ];

			document.getElementById ( this.name + "@" + n + ":htmled" ).value = htmled_getText ( this.name + "@" + n );
		}
		*/
	};
	// }}}
	// {{{ _render_all ()
	this._render_all = function ()
	{
		this._widgets.iterate ( function ( v, k )
		{
			if ( v [ "render" ] )
				v.render ();

			if ( v [ "_refresh" ] )
				v._refresh ();
		} );

		/*
		// I have to use lists and not dicts because
		// the iterate function skips functions
		var t, l = this._suggests.length;
		for ( t = 0; t < l; t ++ )
			this._suggests [ t ] [ 1 ] ( this._suggests [ t ] [ 0 ] );
		*/
	};
	// }}}
	
	// ===========================================================
	// INTERNAL METHODS
	// ===========================================================
	// {{{ _start_field ( vars )
	this._start_field 	= function ( vars, kind )
	{
		var sty = '';
		var descr = vars.get ( "os3_class_descr", "descr" );
		var value = vars.get ( "os3_class_value", "value" );

		if ( ! vars [ 'name' ] )
		{
			this._get_name ( vars );
			vars [ 'ignore' ] = true;
		}

		this._fields.push ( vars [ 'name' ] );

		if ( vars.get ( 'ignore' ) ) this._ignore [ vars [ 'name' ] ] = true;

		if ( vars [ 'style' ] ) sty = 'style="' + vars [ 'style' ] + '"';	

		if ( ( ! this.no_table ) && ( ! this._row_open ) ) 
		{
			if ( vars [ 'os3_full' ] )
			{
				this._row_open = true;
				this.html += '<tr><td colspan="100" align="center" width="100%" class="' + descr + '" ' + sty + '>';
				return;
			} else
				this.html += '<tr><td class="' + descr +'" ' + sty + '>';
		}

		this._row_open = true;

		if ( vars [ 'label' ] ) 
		{
			if ( vars [ 'akey' ] )
			{
				var re = new RegExp ( "(" + vars [ 'akey' ] + ")", "i" );
				if ( ! re.test ( vars [ 'label' ] ) ) vars [ 'label' ] += " (" + vars [ 'akey' ] + ")";

				this.html  += '<label>' + vars [ 'label' ].replace ( re, '<span class="akey">$1</span>' ) + ": </label>";
			} else 
				this.html  += '<label>' + vars [ 'label' ] + ": </label>";
		}

		if ( ! this.no_table ) 
		{
			this.html += '</td><td class="' + value + '" valign="top"'; 

			if ( vars [ 'colspan' ] )
				this.html += ' colspan="' + vars [ 'colspan' ] + '"'

			this.html += '>';
		}
	};
	// }}}
	// {{{ _create_field ( form_element )
	this._create_field = function ( form_element )
	{
		var self = this;

		function _create_vars ( form_element )
		{
			var vars = {};

			self._skips.iterate ( function ( v ) 
			{
				vars [ v ] = form_element.getAttribute ( v );
			} );

			[ 'name', 'id', 'type' ].iterate ( function ( v )
			{
				vars [ v ] = form_element.getAttribute ( v );
			} );

			self._set_values ( vars [ 'type' ], vars );
			self._fields.push ( vars [ 'name' ] );

			return vars;
		}

		var vars = _create_vars ( form_element );
		var skips = "{" + this._skips.join ( '}{' ) + "}";
		var k;

		form_element.id = this.name + "@" + this._get_name ( vars );

		if ( ( vars [ 'defval' ] != undefined ) && ( vars [ 'value' ] == undefined ) ) vars [ 'value' ] = vars [ 'defval' ];

		if ( vars [ 'value' ] ) vars [ 'value' ] = ( vars [ 'value' ] + '' ).replace ( /"/g, '&quot;' );
		
		if ( vars [ 'filter' ] ) 
		{
			form_element.onkeypress=function ( event ) { 
				return filter_valid_chars ( event, vars [ 'filter' ], false );
			};
		}

		if ( ( vars [ 'type' ] == 'checkbox' || vars [ 'type' ] == 'radio' ) && vars [ 'checked' ] ) form_element.setAttribute ( "checked", "checked" );

		if ( vars [ 'akey' ] )   	form_element.setAttribute ( 'accesskey', vars [ 'akey' ] );
		if ( vars [ 'mandatory' ] )	this._mandatory.push ( vars [ 'id' ] );
		if ( vars [ 'validator' ] )
		{

			if ( vars [ 'validator' ] == true )
			{
				if ( vars [ 'os3_def_validator' ] ) this._validates.push ( [ vars [ 'id' ], vars [ 'os3_def_validator' ] ] );
			} else {
				this._validates.push ( [ vars [ 'id' ], vars [ 'validator' ] ] );
			}
		}
	};
	// }}}

	// {{{ _add_field ( kind, vars )
	this._add_field	= function ( kind, vars )
	{
		var s = '<input type="' + kind + '" ';
		//var k;
		//var skips = "{" + this._skips.join ( '}{' ) + "}";
		//var evt_names = "{" + this._evt_names.join ( '}{' ) + "}";
		var name;

		if ( ! vars [ 'id' ] )
			vars [ 'id' ] = this.name + "@" + this._get_name ( vars );
		else
			vars [ 'id' ] = this.name + "@" + vars [ 'id' ];

		name = vars [ 'id' ];

		if ( ( vars [ 'defval' ] != undefined ) && ( vars [ 'value' ] == undefined ) ) vars [ 'value' ] = vars [ 'defval' ];

		/*
		if ( vars [ 'suggest' ] ) 
		{
			if ( typeof AutoSuggest != "undefined" )
				this._suggests.push ( [ vars [ 'id' ], vars [ 'suggest' ] ] );
			else
				console.warn ( "AutoSuggest requested for %s but not included", vars [ 'id' ] );
		}
		*/

		if ( vars [ 'value' ] ) 
		{
			vars [ 'value' ] = ( vars [ 'value' ] + '' ).replace ( /"/g, '&quot;' );
			// console.debug ( "VALUE: %s", vars [ 'value' ] );
		}

		
		s += this._parse_vars ( vars, this._skips );
		/*
		for ( k in vars )
		{
			if ( typeof vars [ k ] == 'function' ) continue;	// Skip functions
			if ( skips.indexOf ( '{' + k.toLowerCase () + '}' ) != -1 ) continue;
			if ( vars [ k ] == undefined ) continue;
			s += k + '="' + vars [ k ] + '" ';
		}
		*/

		if ( vars [ 'filter' ] ) s += ' onkeypress="return filter_valid_chars ( event, \'' + vars [ 'filter' ] + '\', false )" ';

		if ( ( kind == 'checkbox' || kind == 'radio' ) && vars [ 'checked' ] ) s += ' checked="checked" ';
		if ( kind == "radio" ) 
		{
			vars [ 'id' ] += "-" + vars [ 'value' ];

			var rads = this._radios [ name ];
			if ( ! rads ) rads = [];
			rads.push ( vars [ 'id' ] );

			this._radios [ name ] = rads;
		}

		if ( vars [ 'akey' ] )   	s += ' accesskey="' + vars [ 'akey' ] + '" ';
		if ( vars [ 'mandatory' ] )	this._mandatory.push ( vars [ 'id' ] );
		if ( vars [ 'validator' ] )
		{

			if ( vars [ 'validator' ] == true )
			{
				if ( vars [ 'os3_def_validator' ] ) this._validates.push ( [ vars [ 'id' ], vars [ 'os3_def_validator' ] ] );
			} else {
				this._validates.push ( [ vars [ 'id' ], vars [ 'validator' ] ] );
			}
		}

		s += ' id="' + vars [ 'id' ] + '"';

		if ( vars [ 'size' ] )
		{
			var _size = parseInt ( vars [ 'size' ], 10 );
			s += ' style="width: ' + ( _size + 1 ) + 'em;"';
		}

		s += ' />';

		this.html += s;
		this._create_hint ( vars );

		if ( ( ! this._first_field ) && ( vars [ 'name' ] ) ) 
			this._first_field = this._get_name ( vars );

		this._newline ( vars );
	};
	// }}}
	// {{{ _add_button ( vars )
	this._add_button = function ( vars )
	{
		var s = '<button ';
		//var k;
		//var skips = "{hint}{akey}{nonl}{os3_full}";  
		var skips = [ 'hint', 'akey', 'nonl', 'os3_full' ];

		vars [ 'id' ] = this.name + "@" + this._get_name ( vars );


		s += this._parse_vars ( vars, skips );
		/*
		for ( k in vars )
		{
			if ( typeof vars [ k ] == 'function' ) continue;	// Skip functions
			if ( skips.indexOf ( '{' + k.toLowerCase () + '}' ) != -1 ) continue;
			s += k + '="' + vars [ k ] + '" ';
		}
		*/

		if ( vars [ 'akey' ] ) s += ' accesskey="' + vars [ 'akey' ] + '" ';

		s += ' type="button">';
		s += vars [ 'value' ];
		s += '</button>';

		this.html += s;
		this._create_hint ( vars );

		this._newline ( vars );
	};
	// }}}
	// {{{ _newline ( vars ) 
	this._newline = function ( vars )
	{
		if ( ( vars [ 'nonl' ] ) || ( ! this._row_open ) ) 
		{
			this.html += '</td><td nowrap="nowrap" class="descr">';
			return;
		}

		this._row_open = false;

		if ( this.no_table ) 
			this.html += '<br />';
		else
			this.html += '</td></tr>';
	};
	// }}}
	// {{{ _create_hint ( vars ) 
	this._create_hint = function ( vars )
	{
		var s, styles = '';
		var l;

		if ( vars [ 'hint' ] )
		{
			l = _ ( "os3_form", "hint" );
			s = '<img class="hint-image" alt="' + vars [ 'hint' ] + '" src="' + liwe._libbase + '/gfx/form/hint.png" onmouseover="this.nextSibling.style.display = \'inline\'" onmouseout="this.nextSibling.style.display = \'none\'" />';
			s += '<span style="display:none" class="hint-container">' + vars [ 'hint' ] + '</span>';
			this.html += s;

			if ( typeof bubble_show == "undefined" ) liwe.utils.append_js ( liwe._libbase + "/bubble.js" );
		}

		if ( vars [ 'mandatory' ] )
		{
			l = _ ( "os3_form", "Mandatory field" );
			s = '<img class="hint-image" alt="' + l + '" title="' + l + '" src="' + liwe._libbase + '/gfx/form/mandatory.png" id="' + vars [ 'id' ] + '_mandatory" />';
			this.html += s;
			styles += ' mandatory'
		}

		if ( vars [ 'validator' ] )
		{
			l = _ ( "os3_form", "Field needs Validation" );

			s = '<img class="hint-image" alt="' + l + '" title="' + l + '" src="' + liwe._libbase + '/gfx/form/okb.png" id="' + vars [ 'id' ] + '_valid" />';
			this.html += s;
		}

		if ( vars [ 'id' ] ) this.html += '&nbsp;<span class="hint' + styles + '" id="' + vars [ 'id' ] + "@hint" + '">&nbsp;</span>';
	};
	// }}}
	// {{{ _get_name ( vars )
	this._get_name = function ( vars )
	{
		this._widget_count ++;
		if ( ! vars [ 'name' ] ) vars [ 'name' ] = "W" + this._widget_count;
		return vars [ 'name' ];
	};
	// }}}
	// {{{ _set_values ( type, vars )
	this._set_values = function ( type, vars )
	{
		switch ( type )
		{
			case "email":
				vars [ 'filter' ] = "0123456789abcdefghijklmnopqrstuvwxyz._@";
				vars [ 'os3_def_validator' ] = liwe.validators.is_email;
				break;

			case "number":
				vars [ 'filter' ] = "-0123456789";
				vars [ 'class'  ] = "number";
				vars [ 'os3_def_validator' ] = liwe.validators.is_integer;
				break;

			case "money":
				vars [ 'filter' ] = "0123456789,.";
				vars [ 'class'  ] = "number";
				if ( ! vars [ 'onblur' ] )
					vars [ 'onblur' ]  = 'this.value = money_format ( this.value )';
				else
					 vars [ 'onblur' ]  = 'this.value = money_format ( this.value  );' + vars [ 'onblur' ] ;

				// vars [ 'os3_def_validator' ] = check_money;

				if ( vars [ 'defval' ] ) vars [ 'defval' ] = Money.fromLongInt ( vars [ 'defval' ] );
				if ( vars [ 'value' ] ) vars [ 'value' ] = Money.fromLongInt ( vars [ 'value' ] );
				break;
		}
	};
	// }}}
	this._parse_vars = function ( vars, skips )
	{
		var lck, k, s = '';
		var evt_names = "{" + this._evt_names.join ( '}{' ) + "}";

		skips = "{" + skips.join ( '}{' ) + "}";
		
		for ( k in vars )
		{
			if ( typeof vars [ k ] == 'undefined' ) continue;
			lck = k.toLowerCase ();
			if ( evt_names.indexOf ( '{' + lck + '}' ) != -1 )
			{
				if ( typeof vars [ k ] != 'string' )
				{
					liwe.form._field_events [ vars [ 'id' ] ] = vars [ k ];
					vars [ k ] = String.format ( "return liwe.form.field_event ( '%s', '%s' )", this.name, vars [ 'id' ] );
				}
			}
			if ( typeof vars [ k ] == 'function' ) continue;	// Skip functions
			if ( skips.indexOf ( '{' + lck + '}' ) != -1 ) continue;
			s += k + '="' + vars [ k ] + '" ';
		}
		return s;
	};

	// ===========================================================
	// INTERNAL ATTRS
	// ===========================================================
	this._row_open		= false;  // Flag T/F to tell if the row has been started
	this._widget_count	= 0;	  // Internal Widget Counter (used to create unique IDs)

	// this._htmled = Array ();
	// this._htmled_names = {};

	// this._htmled_manager = null;

	this._mandatory = Array ();
	this._validates = Array ();
	this._fields = [];
	this._widgets = {};
	// this._suggests = [];

	this._skips = [ 'label','hint','filter','akey','mandatory','validator','os3_def_validator','nonl','os3_full','defval','checked','id', 'options' ];
	this._evt_names = [ 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmousemove', 'onmouseover', 'onmouseout', 'onkeydown', 'onkeyup', 'onkeypress', 'onchange', 'onfocus', 'onblur' ];

	liwe.form._instances  [ name ] = this;
};
// }}}
// {{{ _aim 
liwe.form._aim = {
	frame : function ( f, c, frm ) 
	{
		var i = document.getElementById ( '__ajax_form' );

		if ( ! i )
		{
			var d = document.createElement ( 'DIV' );
			d.innerHTML = '<iframe style="display:none" src="about:blank" id="__ajax_form" name="__ajax_form" onload="liwe.form._aim.loaded()"></iframe>';
			document.body.appendChild(d);
 
			i = document.getElementById ( '__ajax_form' );
		}

		if ( c && typeof ( c.onComplete ) == 'function') i.onComplete = c.onComplete;
		i._form = frm;
	},
 
	form : function ( f ) {
		f.setAttribute ( 'target', '__ajax_form' );
		f.setAttribute ( 'enctype', 'multipart/form-data' );
	},
 
	submit : function ( f, c ) 
	{
		var frm = liwe.form.get ( f.getAttribute ( "name" ) );

		if ( ! frm.check () ) return false;

		liwe.form._aim.form ( f, liwe.form._aim.frame ( f, c, frm ) );

		if ( c && typeof ( c.onStart ) == 'function' ) 
			return c.onStart ( frm );

		return true;
	},
 
	loaded : function () 
	{
		var i = document.getElementById( '__ajax_form' );
		var d, f = i._form;

		if ( i.contentDocument ) 
		{
			d = i.contentDocument;
		} else if (i.contentWindow) {
			d = i.contentWindow.document;
		} else {
			d = window.frames [ '__ajax_form' ].document;
		}

		if ( d.location.href == "about:blank" ) return;

		// fix html entities
		f._fields.iterate ( function ( field )
		{
			var el = f.get_element ( field );
			if ( ! el || ( el.type != 'text' && el.tagName != 'TEXTAREA' ) ) return;
			el.value = el.value.entities2char ();
		} );
 
		if ( typeof ( i.onComplete ) == 'function' ) i.onComplete ( i._form, d.body.innerHTML );
	}
 
};
// }}}
// {{{ _event_on_start ( frm )
liwe.form._event_on_start = function ( frm )
{
	if ( frm.events [ 'start' ] ) 
		if ( ! frm.events [ 'start' ] ( frm ) ) return false;

	return true;
};
// }}}
// {{{ _event_on_complete ( frm, vals )
liwe.form._event_on_complete = function ( frm, vals )
{
	if ( frm.easy )
	{
		AJAXManager.handle_easy ( vals, frm.events [ 'complete' ] );
	} else {
		if ( frm.events [ 'complete' ] ) frm.events [ 'complete' ] ( vals );
	}
}; 
// }}}
